package controller;

import java.awt.Point;
import java.io.File;
import java.util.ArrayList;

import javax.swing.JOptionPane;

import model.Circuit;
import model.Decoder;
import model.GGA;
import model.GPS;
import model.Lap;
import model.RMC;
import model.Session;
import view.ViewSession;
import database.ConectorDB;

public class Controladora {
	
	private ConectorDB conn;
	private Decoder decoder;
	private ArrayList<double[]> coordCartesianas = new ArrayList<double[]>();
	private double[] startingPoint = {0, 0};
	private double[] endingPoint = {0, 0};
	private Circuit circuit;
	private ArrayList<Session> sessions = new ArrayList<Session>();
	private ViewSession viewSession;

	public Controladora(ViewSession viewSession, ConectorDB conn) {
		this.conn = conn;
		decoder = new Decoder();
		circuit = new Circuit();
		this.viewSession = viewSession;
	}
	
	/**
	 * Comprova si l'usuari existeix i la contraseña es correcte a la base de dades.
	 * @param usuario
	 * @param password
	 * @return true si existeix, false si no existeix.
	 */
	public boolean login(String usuario, String password) {
		conn.connect();
		boolean accessGranted = false;
		if(conn.checkLogin(usuario, password)){
			accessGranted =  true;
		}
		conn.disconnect();
		return accessGranted;
	}
	
	/**
	 * Comprova si l'usuari existeix a la base de dades.
	 * @param usuario
	 * @return true si existeix, false si no existeix.
	 */
	public boolean checkUser(String usuario) {
		conn.connect();
		boolean accessGranted = false;
		if(conn.checkUser(usuario)){
			accessGranted =  true;
		}
		conn.disconnect();
		return accessGranted;
	}
	
	/**
	 * Insereix l'usuari a la base de dades
	 * @param usuario
	 * @param password
	 */
	public boolean registerUser(String usuario, String password) {
		conn.connect();
		boolean resultat = conn.registraUsuari(usuario, password);
		conn.disconnect();
		
		return resultat;
	}
	
	/**
	 * Es neteja les dades de l'usuari anterior.
	 */
	public void resetDades() {
		circuit = new Circuit();
		coordCartesianas = new ArrayList<double[]>();
		startingPoint[0] = 0;
		startingPoint[1] = 0;
		endingPoint[0] = 0;
		endingPoint[1] = 0;
	}
	
	/**
	 * Carrega les sessions de l'usuari donat un circuit.
	 */
	public void carregaDades(){
		
		int nSessionsCarregades = sessions.size();
		boolean carregaCorrecte = false;
		String circuit = "none";
		conn.connect();
		String[] circuits = conn.obtenirCircuits(viewSession.getUsuari());
		if (circuits.length > 1) {
			circuit = (String) JOptionPane.showInputDialog( null,  "Choose a circuit",
			        "Circut chooser",
			        JOptionPane.QUESTION_MESSAGE, 
			        null, 
			        circuits, 
			        circuits[0]);
			
			carregaCorrecte = conn.carregaResum(viewSession.getUsuari(), circuit, sessions);
		}
		if (carregaCorrecte && circuit != null && sessions.size() != nSessionsCarregades) {
			viewSession.showTab(0);
			viewSession.showTab(3);
		} else if (circuit != null && !circuit.equals("none")){
			JOptionPane.showMessageDialog(null, "Session not found!", "ERROR", JOptionPane.ERROR_MESSAGE);
		} else if (circuit != null && circuit.equals("none")) {
			JOptionPane.showMessageDialog(null, "You have no sessions!", "ERROR", JOptionPane.ERROR_MESSAGE);
		}
		conn.disconnect();
	}
	
	/**
	 * Genera un objecte Session amb les dades del circuit.
	 * @param sessionName
	 * @return l'objecte Session amb les dades.
	 */
	private Session generaSessio(String sessionName) {
		
		Session s = new Session();
		s.setName(sessionName);
		s.setAvgAvgSpeed(circuit.getStatistics().averageAverageSpeed(circuit.getLaps()));
		s.setAvgMinSpeed(circuit.getStatistics().averageMinimumSpeed(circuit.getLaps()));
		s.setAvgTopSpeed(circuit.getStatistics().averageTopSpeed(circuit.getLaps()));
		s.setAvgTime(circuit.getStatistics().averageTime(circuit.getLaps()).getValor());
		s.setCircuit(circuit.getName());
		s.setUsername(viewSession.getUsuari());
		s.setMinTime(circuit.getStatistics().getBestTime().getValor());
		
		return s;
	}
	
	/**
	 * Puja les dades de la sessió actual a la base de dades
	 * @param sessionName
	 * @return true si s'han pujat correctement, false si no.
	 */
	public boolean pujaDades(String sessionName){
		Session session = generaSessio(sessionName);
		conn.connect();
		boolean carregaCorrecte = conn.pujaResum(viewSession.getUsuari(), circuit, session);
		conn.disconnect();
		
		return carregaCorrecte;
	}
	
	/**
	 * Busca el circuit rebut a la carpeta del projecte i si el troba guarda les dades.
	 * @param file
	 * @return true si s'ha trobat i carregat, false si no s'ha trobat.
	 */
	public boolean searchFile(String file) {
		File f = new File(file + ".nmea");
		
		if (f.exists()) {
			circuit = new Circuit();
			circuit.setName(file);
			generarCoordenadesCircuit(file + ".nmea");
			viewSession.initPlotCircuit();
			return true;
		}
		
		return false;
	}
	
	/**
	 * Transforma les coordenades GPS en coordenades cartesianes
	 * 
	 */
	public void generarCoordenadesCircuit(String file) {
		if(decoder.loadFile(file)) {
			GPS gps = decoder.getGps();
			coordCartesianas = new ArrayList<double[]>();
			ArrayList<GGA> ggas = gps.getGgas();
			double xMax = 0;
			double yMax = 0;
			double xMin = Double.MAX_VALUE;
			double yMin = Double.MAX_VALUE;
			double xCentro;
			double yCentro;
			
			double coordAux[]; 
			
			for (int i = 0; i < ggas.size(); i++) {
				GGA ggaAux = ggas.get(i);
				
				double latitude = ggaAux.getLatitude().getGraus() + ggaAux.getLatitude().getMinuts()/60;
				double longitude = ggaAux.getLongitude().getGraus() + ggaAux.getLongitude().getMinuts()/60 ;
				
				if (ggaAux.getLatitude().getOrientacio().equalsIgnoreCase("S")) {
					latitude = -latitude;
				}
				if (ggaAux.getLongitude().getOrientacio().equalsIgnoreCase("W")) {
					longitude = -longitude;
				}
				
				coordAux = decoder.convertSphericalToCartesian(latitude, longitude);
				
				if (xMax < Math.abs(coordAux[0])) {
					xMax = coordAux[0];
				} 
				if (yMax < Math.abs(coordAux[1])) {
					yMax = coordAux[1];
				} 
				if (xMin > Math.abs(coordAux[0])) {
					xMin = coordAux[0];
				} 
				if (yMin > Math.abs(coordAux[1])) {
					yMin = coordAux[1];
				} 
				
				ggaAux.setCartesiana(coordAux);
				coordCartesianas.add(coordAux);
			}
			
			xCentro = (xMax - xMin)/2;
			yCentro = (yMax - yMin)/2;
			
			coordCartesianas.get(0)[0] = ((coordCartesianas.get(0)[0] - xMin) - xCentro) * 500 + 512;
			coordCartesianas.get(0)[1] = ((coordCartesianas.get(0)[1] - yMin) - yCentro) * 500 + 300;
			
			for (int i = 0; i < coordCartesianas.size() - 1;i++) {
				
				if (i != coordCartesianas.size() - 1) {	
					coordCartesianas.get(i+1)[0] = ((coordCartesianas.get(i+1)[0] - xMin) - xCentro) * 500 + 512;
					coordCartesianas.get(i+1)[1] = ((coordCartesianas.get(i+1)[1] - yMin) - yCentro) * 500 + 300;
				}
				
			}
			
		}
	}
	
	/**
	 * Calcula el punt de sortida mes proper a on l'usuari a clicat
	 * @param clickedX
	 * @param clickedY
	 * @return Les coordenades del circuit corresponents a la sortida.
	 */
	public double[] calculaPuntSortida(int clickedX, int clickedY) {
		
		Point p = new Point(clickedX, clickedY);
		double distance = 10000;
		
		for (int i = 0; i < coordCartesianas.size() - 1;i++) {
			
			double auxDistance = p.distance(coordCartesianas.get(i)[0], coordCartesianas.get(i)[1]);
			
			if (auxDistance < distance) {
				distance = auxDistance;
				startingPoint[0] = coordCartesianas.get(i)[0];
				startingPoint[1] = coordCartesianas.get(i)[1];
			}
		}
		
		return startingPoint;
	}
	
	/**
	 * Calcula el punt final mes proper a on l'usuari a clicat
	 * @param clickedX
	 * @param clickedY
	 * @return Les coordenades del circuit corresponents al final del tram.
	 */
	public double[] calculaPuntFinal(int clickedX, int clickedY) {
		
		Point p = new Point(clickedX, clickedY);
		double distance = 10000;
		
		for (int i = 0; i < coordCartesianas.size() - 1;i++) {
			
			double auxDistance = p.distance(coordCartesianas.get(i)[0], coordCartesianas.get(i)[1]);
			
			if (auxDistance < distance) {
				distance = auxDistance;
				endingPoint[0] = coordCartesianas.get(i)[0];
				endingPoint[1] = coordCartesianas.get(i)[1];
			}
		}
		
		return endingPoint;
	}
	
	/**
	 * Calcula el numero de voltes que s'ha donat al circuit i omple la llista de laps amb totes la informacio pertinent a cada lap.
	 * @return El numero del voltes
	 */
	public int numeroDeVoltes() {
		viewSession.enableUploadDB();
		int numVoltes = -1;
		int auxVoltes = 0;
		double dMin = 10000;
		Point p = new Point((int)startingPoint[0], (int)startingPoint[1]);
		ArrayList<GGA> auxGGAs = new ArrayList<GGA>();
		ArrayList<RMC> auxRMCs = new ArrayList<RMC>();
		Lap lap = new Lap();
		GPS gps = decoder.getGps();
		ArrayList<GGA> ggas = gps.getGgas();
		ArrayList<RMC> rmcs = gps.getRmcs();
		
		for (int i = 0; i < coordCartesianas.size() - 1;i++) {
			
			double[] aux = coordCartesianas.get(i);
			
			double distancia = p.distance(aux[0], aux[1]);
			
			if (distancia < dMin && distancia > 10) {
				dMin = distancia;
				
			}else if(distancia < dMin && distancia < 10){
				
				numVoltes++;
				dMin=10000;
				
				if (numVoltes > 0) {
					for (int j = auxVoltes; j < i && (j < coordCartesianas.size() - 1); j++) {
						auxGGAs.add(ggas.get(j));
						auxRMCs.add(rmcs.get(j));
					}
					lap.setMostrar(true);
					lap.setGgas(new ArrayList<GGA>(auxGGAs));
					lap.setRmcs(new ArrayList<RMC>(auxRMCs));
					lap.setRgb((int)(Math.random()*255), (int)(Math.random()*255), (int)(Math.random()*255));
					
					circuit.getLaps().add(lap);
					lap = new Lap();
					auxGGAs.clear();
					auxRMCs.clear();
				}
				auxVoltes = i;
				i+=50;
			}
		}
		
		circuit.getStatistics().bestStats(circuit.getLaps());
		
		return numVoltes;
	}
	
	/**
	 * Calcula el numero de trams que s'ha donat al circuit i omple la llista de laps amb totes la informacio pertinent a cada tram.
	 * @return El numero del trams
	 */
	public int numeroDeTrams() {
		viewSession.enableUploadDB();
		int numVoltes = -1;
		int auxVoltes = 0;
		int trams = 0;
		double dMin = 10000;
		Point p = new Point((int)startingPoint[0], (int)startingPoint[1]);
		Point pEnd = new Point((int)endingPoint[0], (int)endingPoint[1]);
		ArrayList<GGA> auxGGAs = new ArrayList<GGA>();
		ArrayList<RMC> auxRMCs = new ArrayList<RMC>();
		Lap lap = new Lap();
		GPS gps = decoder.getGps();
		ArrayList<GGA> ggas = gps.getGgas();
		ArrayList<RMC> rmcs = gps.getRmcs();
		
		for (int i = 0; i < coordCartesianas.size() - 1;i++) {
			
			double[] aux = coordCartesianas.get(i);
			
			
			double distancia;
			
			if (trams % 2 == 0) {
				distancia = p.distance(aux[0], aux[1]);
			} else {
				distancia = pEnd.distance(aux[0], aux[1]);
			}
			
			if (distancia < dMin && distancia > 10) {
				dMin = distancia;
				
			}else if(distancia < dMin && distancia < 10){
				
				numVoltes++;
				dMin=10000;
				
				if (numVoltes > 0 && trams % 2 != 0) {
					for (int j = auxVoltes; j < i && (j < coordCartesianas.size() - 1); j++) {
						auxGGAs.add(ggas.get(j));
						auxRMCs.add(rmcs.get(j));
					}
					lap.setMostrar(true);
					lap.setGgas(new ArrayList<GGA>(auxGGAs));
					lap.setRmcs(new ArrayList<RMC>(auxRMCs));
					lap.setRgb((int)(Math.random()*255), (int)(Math.random()*255), (int)(Math.random()*255));
					
					circuit.getLaps().add(lap);
					lap = new Lap();
					auxGGAs.clear();
					auxRMCs.clear();
				}
				auxVoltes = i;
				trams++;
			}
		}
		
		circuit.getStatistics().bestStats(circuit.getLaps());
		
		return numVoltes;
	}
	
	/**
	 * @return coordCartesianas
	 */
	public ArrayList<double[]> getCoordCartesianas() {
		return coordCartesianas;
	}
	
	/**
	 * 
	 * @return circuit
	 */
	public Circuit getCircuit() {
		return circuit;
	}

	/**
	 * 
	 * @return ConcetorDB
	 */
	public ConectorDB getConn() {
		return conn;
	}

	/**
	 * 
	 * @param conn
	 */
	public void setConn(ConectorDB conn) {
		this.conn = conn;
	}

	/**
	 * @return the sessions
	 */
	public ArrayList<Session> getSessions() {
		return sessions;
	}

	/**
	 * Activa la visibilitat de una volta.
	 * @param volta
	 * @param b
	 */
	public void voltaActiva(int volta, boolean mostrar){
		circuit.getLaps().get(volta).setMostrar(mostrar);
	}
	
}
